<?php
declare (strict_types = 1);
namespace app\middleware;
use think\Response;
use think\Config;
use think\facade\Log;
/**
 * 中间件：控制接口访问频次
 */
class RateLimit
{
    private $config;
    private $maxRequests = 1000;
    private $period = 10;
    private $prefix = 'fortuna';
    public function __construct(Config $config)
    {
        $this->config = $config->get('redis', [
            'scheme'=>'tcp',
            'host'=>'127.0.0.1',
            'port'=>'6379'
        ]);
        // $this->block_ip_list = $config->get('ip.block', []);
    }
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        if(!$this->check()){
            return Response::create()->code(403);
        }else{
            return $next($request);
        }
    }
    private function check($use =  1.0){
        // PRedis 实例
        $client = new \Predis\Client($this->config);
        if(!$client->isConnected()){
            return true;
        }
        // 计算速率
        $rate = $this->maxRequests / $this->period;
        Log::record('速率: '.$rate, 'info');
        // 记录的时间戳
        $time_key = $this->prefix . ':time';
        $allowance_key = $this->prefix . ':allowance';

        if(!$client->exists($time_key)){
            Log::record('不存在: '.$time_key.'，开始写入当前时间', 'info');
            $client->set($time_key, (string) time(), "ex", $this->period);
            $client->set($allowance_key, ($this->maxRequests - $use), "ex", $this->period);
            return true;
        }
        // 当前时间戳
        $current_time = time();
        Log::record('当前时间: '.$current_time, 'info');
        // 计算时间间隔
        $time_passed = $current_time - $client->get($time_key);
        Log::record('时间间隔: '.$time_passed, 'info');
        //计算添加令牌
        $allowance = $client->get($allowance_key);
        Log::record('剩余令牌: '.$allowance, 'info');
        $allowance += $time_passed * $rate;
        Log::record('补充后的令牌: '.$allowance, 'info');

        // 令牌桶溢出则归零
        if ($allowance > $this->maxRequests) {
            Log::record('令牌桶溢出: '.$this->maxRequests, 'info');
            $allowance = $this->maxRequests; // throttle
        }

        if ($allowance < $use) {
            // need to wait for more 'tokens' to be in the bucket.
            $client->set($allowance_key, $allowance, 'ex', $this->period);
            Log::record('令牌桶不足: '.$allowance, 'info');
            return false;
        }
        $client->set($allowance_key, ($allowance - $use), 'ex', $this->period);
        Log::record('完成消费: '.($allowance - $use), 'info');
        Log::record('----------------------------------------------');
        return true;
    }
}
